/**
 * 
 */
package mdp.solve;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ArrayBlockingQueue;

import util.Pair;
import util.Timer;

import dd.discrete.DD;
import mdp.id.ADDHandler;
import mdp.id.RDDL2DD;
import mdp.id.RDDL2DD.DEBUG;
import mdp.id.RDDL2DD.ORDER;

/**
 * @author NR
 *
 * TODO
 */
public class ValueIterationCutsetConditioning<T extends RDDL2DD>  implements Runnable{
	
	private static double	EPSILON	= 0;
	private T _handler;
	private ArrayBlockingQueue<Pair<T, Integer>> _bq = null;
	
	private double BIGADD = 10000;
	private static final boolean	TIMING_ON	= false;
	
	private Set<Map<Integer, Boolean>> cuts = new HashSet<Map<Integer,Boolean>>();
//	private DTree _dtree;
	/**
	 * 
	 */
	private ArrayList<String>	_maxOrder;
	private ArrayList<String>	_sumOrder;
	private ArrayList<Pair<Integer, String>>	_scores;
	
	private Timer	scoreTime = new Timer();
	private Timer	maxTime = new Timer();
	private Timer	expectTime = new Timer();
	private Timer rewTime = new Timer();
	private Timer	setupTime = new Timer();
	private Timer	postTime = new Timer();
	private Timer	maxOutTime = new Timer();
	private Timer	bigLoop = new Timer();
	private Timer	flushTime = new Timer();
	private Timer fixTime = new Timer();
	private Timer threshTime = new Timer();
	private Timer convertTime = new Timer();
	private Timer restrictTime = new Timer();
	private Timer	_timer = new Timer();
	private Timer	cptTimer = new Timer();

	public ValueIterationCutsetConditioning(T handler, String domain, String instance, double epsilon,
			double bigadd,
			ArrayBlockingQueue<Pair<T, Integer>> bq) {
		_bq = bq; 
		EPSILON = epsilon;
		_handler = handler;
		_handler.buildFromFile(domain, instance);
		_sumOrder = handler.getSumOrderString();
		_maxOrder = handler.getMaxOrderString();
		BIGADD = bigadd;
//		_dtree = new DTree<T>(handler, new HashSet<String>(_sumOrder), _maxOrder );
	}

	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
//	@Override
	public void run() {
		
		int _valueDD = _handler.DD_ZERO;
		int _policy = _handler.DD_ZERO;
		int iter = 0;
		boolean done = false;
		boolean lastiter = false;

		Timer solutionTime = new Timer();
		
		if( TIMING_ON ) {
			scoreTime.ResumeTimer();	
		}
		_scores = _handler.getScores(new HashMap<Integer, Boolean>(), _sumOrder.size());
		if( TIMING_ON ) {
			scoreTime.PauseTimer();	
		}
		
		
		Timer iterTime = new Timer();
		
		while( !done ) {
			if( TIMING_ON ) {
				iterTime.ResumeTimer();	
			}
			
			solutionTime.ResumeTimer();
			
			int Vprev = _valueDD;
			int Vprimed = _handler.swapForPrimes(_valueDD);
			_handler.saveThis(Vprev);
			
			if( TIMING_ON ) {
				_timer.ResetTimer();
			}
			Pair<Integer, Integer> vq = RCRegress(Vprimed, Vprev, _policy,
											_handler.isHorizon(iter+1));

			if( TIMING_ON ) {
				System.out.println("Time for regression " + _timer.GetTimeSoFarAndResetInMinutes() );
			}
			
			_valueDD = vq._o1;
			_policy = vq._o2;
			
			int diff = _handler._context.applyInt(_valueDD, Vprev, DD.ARITH_MINUS);
			
			double error = Math.max(
					Math.abs(_handler.getMax(diff)), 
					Math.abs(_handler.getMin(diff) == _handler.getNegInf() ? 0 : _handler.getMin(diff)));
			++iter;
			
			if( lastiter ) {
				done = true;
			}
			
			if( error < EPSILON || _handler.isHorizon(iter+1) ) {
				lastiter = true;
			}
			
			_handler.forgetThis(Vprev);
			_handler.flushCaches(true, _valueDD, _policy);
			
			if( TIMING_ON ) {
				iterTime.PauseTimer();
			}
			
			solutionTime.PauseTimer();
			
			System.out.println("Iteration " + iter + " " + error + " " + solutionTime.GetElapsedTimeInMinutes() + " " + _handler.getSize(_valueDD));
			
			if( TIMING_ON ) {
				System.out.println("restrict time " + restrictTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("score time " + scoreTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("max time " + maxTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("exp time " + expectTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("rew time " + rewTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("setup time " + setupTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("post time " + postTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("max out time " + maxOutTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("big time " + bigLoop.GetElapsedTimeInMinutesAndReset() );
				System.out.println("flush time " + flushTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("time total : " + iterTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("fix time : " + fixTime.GetElapsedTimeInMinutesAndReset() );
				System.out.println("Thresh time : " + threshTime.GetElapsedTimeInMinutesAndReset() );
		//		restrictTime = scoreTime = maxTime = expectTime = rewTime 
		//		= setupTime = postTime = maxOutTime = bigLoop = flushTime = 0;
			}
			
//			_handler._context.showCacheSize();
//			System.out.println(_handler._context.applyHit);
			
		}
//		System.out.println(timetosolve);
//		int _policyDD = _handler.makePolicy(vq._o2);
		solutionTime.ResumeTimer();
		_policy = _handler.makePolicyHelper(_policy, new HashMap<String, Boolean>());
		solutionTime.PauseTimer();
		
		_handler.setValueDD(_valueDD);
		_handler.setPolicyDD(_policy);
		
		ArrayList stats = new ArrayList();
		
		try{
			stats = _handler.playDD(_policy);
		}catch(Exception e ) {
		}
		
		stats.add(cptTimer.GetElapsedTimeInMinutes());
		stats.add(solutionTime.GetElapsedTimeInMinutes());
		stats.add(_handler.getSize(_valueDD));
		stats.add(_handler.getSize(_policy));
		
		System.out.println(stats);
	
//		_handler.showGraph(_policyDD);
		
		if( _bq != null ) {
			try
			{
				_bq.put(new Pair<T, Integer>(_handler, _valueDD));
//				_bq.put(new Pair<T, Integer>(_handler, _policy));
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		
		}
	}
	
	/**
	 * @param pVprimed
	 * @param pScores 
	 * @param pObject
	 * @return
	 */
	private Pair<Integer, Integer> RCRegress(int pVprimed, int Vprev, int polPrev,
												boolean makepolicy) {

		Pair<Integer, Integer> ret = _handler.regressExogenous(pVprimed, null, true, Vprev, polPrev);
		
		int vp = ret._o1;
		int sumInd = ret._o2;
		int maxInd = 0;
		
		//regress control vars
		Pair<Integer, Integer> vx = regressControl(vp, sumInd, Vprev, polPrev, makepolicy);
		
		return vx;
		
	}
	
	/**
	 * @param pVp
	 * @param pSumInd
	 * @param pMaxInd
	 * @param scores 
	 * @param pHashMap
	 * @param pRoot
	 * @param pHorizon
	 * @return
	 */
	private Pair<Integer, Integer> regressControl(int pVp, int pSumInd, int Vprev,
													int polPrev, boolean makepolicy) {

		//for each x'
		//	if size > BIGADD
		//		ask curnode for cutset
		//		for each initialization to cutset
		//			add to ass
		//			call regCon with dd
		//			max with cur val fn.
		//			update policy if need be
		//			remove from ass
		//			return
		//	else 
		//		continue regression
		//discount and add reward
		//store X
		//max out actions vars
		//make policy
		//return v,q
		
//		pVp = _handler.ConstrainThis(pVp, null, true, true, Vprev, polPrev);
		
		Stack<Pair<Integer, Pair<Integer, Map<Integer, Boolean>>>> stack 
			= new Stack<Pair<Integer, Pair<Integer, Map<Integer, Boolean>>>>(); 
		
		for( Map<Integer, Boolean> cut : cuts ) {
			if( TIMING_ON ) {
				restrictTime.ResumeTimer();
			}
			
			int pushd = _handler.ConstrainThis(pVp, cut, true, false);
			
			stack.push(new Pair<Integer, Pair<Integer, Map<Integer, Boolean>>>
				( pushd, 
						new Pair<Integer, Map<Integer, Boolean>>(pSumInd,cut)));
			if( TIMING_ON ) {
				restrictTime.PauseTimer();
			}
		}

		if( stack.empty() ) {
			HashMap<Integer, Boolean> act = new HashMap<Integer, Boolean>();
			int pushd = _handler.ConstrainThis(pVp, act, true, false);
			stack.push(new Pair<Integer, Pair<Integer, Map<Integer, Boolean>>>(pushd, 
						new Pair<Integer, Map<Integer, Boolean>>(pSumInd, 
					act)));
		}

		flushCaches(stack, Vprev, polPrev);	
		
		cuts.clear();
		
		int Vcur = Vprev;
		int policy = polPrev;
		
		while( !stack.empty() ) {
			
//			System.out.println(stack);
			
			Pair<Integer, Pair<Integer, Map<Integer, Boolean>>> curr = stack.pop();

			int vp = curr._o1;
			boolean expected = false;
			
			Map<Integer, Boolean> ass = curr._o2._o2;
			int i = curr._o2._o1;
			
//			vp = _handler.simplify(vp, ass);
			
			if( TIMING_ON ) {
				bigLoop.ResumeTimer();
			}
			
			for( ; i < _sumOrder.size(); ++i ) {
				if( ass.size() != _maxOrder.size() ) {
					long size = _handler.getSize(vp);
					
					if( size > BIGADD ) {
						
						if( TIMING_ON ) {
							scoreTime.ResumeTimer();
						}
						ArrayList<Pair<Integer, String>> scores 
							= getScores(ass);
						if( TIMING_ON ) {
							scoreTime.PauseTimer();
						}
						
						if( scores.size() != 0 ) {
							
							setupTime.ResumeTimer();
							
							String var = scores.get(0)._o2;
							int id = _handler.toID(var);
							
//							System.out.println("enumerating " + var );

							if( TIMING_ON ) {
								restrictTime.ResumeTimer();
							}
							
							cptTimer.ResumeTimer();
							
							ArrayList<Map<Integer, Boolean>> ret 
								= _handler.convertToBN(ass, id, false);
							
							cptTimer.PauseTimer();
							
							for( Map<Integer, Boolean> a : ret ) {
								int simpl = _handler._context.restrict(vp, 
										_handler.toContextID(var), 
										a.get(id) ? DD.RESTRICT_HIGH : DD.RESTRICT_LOW);
								stack.push(new Pair<Integer, Pair<Integer,Map<Integer,Boolean>>>(
												simpl, 
												new Pair<Integer, Map<Integer, Boolean>>(i, a)));
							}
							
							if( TIMING_ON ) {
								restrictTime.PauseTimer();
							}
							
//							flushCaches(stack, Vcur, policy, polPrev);
							
							if( TIMING_ON ) {
								setupTime.PauseTimer();
							}
							break;

						}
						
					}
				}
				
				String var = _sumOrder.get(i);
				if( TIMING_ON ) {
					expectTime.ResumeTimer();
				}
				vp = _handler.computeExpectation(vp, _handler.toID(var), ass, false);
				expected = true;
				if( TIMING_ON ) {
					expectTime.PauseTimer();				
				}
				
				flushCaches(stack, vp, Vcur, policy);
				
			}
			
			if( TIMING_ON ) {
				bigLoop.PauseTimer();
			}
			
			if( i != _sumOrder.size() ) {
				continue;
			}else {
				
				
				if( TIMING_ON ) {
					rewTime.ResumeTimer();
				}
				
				if( !expected ){
					System.out.println("it happened");
				}
				
				int saver = vp;
				_handler.saveThis(saver);
				vp = _handler.DiscountAndAddReward(vp, ass, false);
				_handler.forgetThis(saver);
				
				if( TIMING_ON ) {
					rewTime.PauseTimer();
				}

				flushCaches(stack, vp, Vcur, policy);
				
				int q = vp;
				
				if( TIMING_ON ) {
					maxOutTime.ResumeTimer();
				}
				vp = _handler.maxOutActionVars(q, ass, false);
				if( TIMING_ON ) {
					maxOutTime.PauseTimer();
				}
				
				flushCaches(stack, vp, q, Vcur, policy);
				
				if( TIMING_ON ){
					fixTime.ResumeTimer();
				}
				vp = _handler.fix(vp);
				if( TIMING_ON ){
					fixTime.PauseTimer();
				}
				
				if( TIMING_ON ) {
					maxTime.ResumeTimer();
				}
				int Vnext = _handler._context.applyInt(Vcur, vp, DD.ARITH_MAX);
				if( TIMING_ON ) {
					maxTime.PauseTimer();
				}
				
				if( TIMING_ON ) {
					postTime.ResumeTimer();				
				}
				
				if( makepolicy ) {
					
					if( TIMING_ON ){
						fixTime.ResumeTimer();
					}
					q = _handler.fix(q);
					if( TIMING_ON ){
						fixTime.PauseTimer();
					}
					
					int diff = _handler._context.applyInt(vp, q, DD.ARITH_MINUS);
					if( TIMING_ON ) {
						threshTime.ResumeTimer();
					}
					int actzero = _handler.thresholdAt(diff, 0.0d);	
					if( TIMING_ON ) {
						threshTime.PauseTimer();
					}

					int actone = _handler._context.applyInt(_handler.DD_ONE, 
									actzero , DD.ARITH_MINUS);
					
					int multdd = _handler.DD_ONE;
					for( Map.Entry<Integer, Boolean> entry : ass.entrySet() ) {
						Integer id = entry.getKey();
						String varname = (String) _handler._tmID2VarName.get(id);
						int dd = _handler._context.getVarNode(_handler.toContextID(varname), 
								entry.getValue()?0.0d:1.0d, entry.getValue()?1.0d:0.0d);
						
						multdd = _handler._context.applyInt(multdd, dd, DD.ARITH_PROD);
					}
					
					int statediff = _handler._context.applyInt(Vnext, vp, DD.ARITH_MINUS);
					
					if( TIMING_ON ) {
						threshTime.ResumeTimer();
					}

					int zeroOut = _handler.thresholdAt(statediff, 0.0d);
					if( TIMING_ON ) {
						threshTime.PauseTimer();
					}

					int oldpol = _handler._context.applyInt(policy, zeroOut, DD.ARITH_PROD);
					
					int newpol = _handler._context.applyInt(multdd, actone, DD.ARITH_PROD);
					int zeroIn = _handler._context.applyInt(_handler.DD_ONE, zeroOut, DD.ARITH_MINUS);
					newpol = _handler._context.applyInt(zeroIn, newpol, DD.ARITH_PROD);
					policy = _handler._context.applyInt(oldpol, newpol, DD.ARITH_SUM);
					
					System.out.println("Leaf node : X " + ass + " " + _handler.getSize(vp));
					
				}
				
				System.out.println("Leaf node : X " + ass + " " + _handler.getSize(vp));
				
				Vcur = Vnext;
				
				cuts.add(ass);
				
				flushCaches(stack, Vcur, policy);
				
				if( TIMING_ON ) {
					postTime.PauseTimer();
				}
			}
				
		}
		
//		System.out.println((System.currentTimeMillis()-whole)/(1000d*60));
		
		return new Pair<Integer, Integer>(Vcur, policy);
	}

	
	/**
	 * @param pAss
	 * @return
	 */
	private ArrayList<Pair<Integer, String>> getScores(
			Map<Integer, Boolean> pAss) {
		ArrayList<Pair<Integer, String>> ret 
			= new ArrayList<Pair<Integer, String>>();
		
		for( Pair<Integer, String> pair :  _scores ) {
			if( pAss.get(_handler.toID(pair._o2)) == null ) {
				ret.add(pair);
			}
		}
		
		return ret;
	}

	/**
	 * @param pB
	 * @param pVp
	 * @param pStack
	 */
	private void flushCaches(
			Stack<Pair<Integer, Pair<Integer, Map<Integer, Boolean>>>> pStack,
			int... pVp) {
		
		flushTime.ResumeTimer();
		int saved[] = new int[pVp.length+pStack.size()];
		System.arraycopy(pVp, 0, saved, 0, pVp.length);
		int k = pVp.length;
		
		for( Pair<Integer, Pair<Integer, Map<Integer, Boolean>>> entry : pStack ) {
			saved[k++] = entry._o1;
		}
		
		
		_handler.flushCaches(false, saved);
		flushTime.PauseTimer();
	}

	/**
	 * @param pCutSet
	 * @return
	 */
	private ArrayList<String> retainActionsOnly(Set<String> pCutSet) {
		Set<String> copy = new HashSet<String>(pCutSet);
		copy.retainAll(_handler._alactionvars);
		return new ArrayList<String>(copy);
	}

	/**
	 * @return
	 */
	

	public static void main(String[] args) throws InterruptedException {
		System.out.println(args[3]);
		
		ADDHandler handle = new ADDHandler(DEBUG.PROBLEM_INFO , ORDER.GUESS);
		Runnable vic = 
			new ValueIterationCutsetConditioning<ADDHandler>(handle, args[0], args[1], 
					Double.parseDouble(args[2]), Double.parseDouble(args[3]), null);
		Thread vicWorker = new Thread(vic, "VIC");
		vicWorker.start();
		vicWorker.join();
	}
	
}
